const { SyncHook } = require("tapable")
const ejs = require('ejs')
const NormalModuleFactory = require("./NormalModuleFactory")
const Parser = require('./Parser')
const async = require('neo-async')
const Chunk = require("./Chunk")

class Compilation {
  constructor(compiler) {
    this.compiler = compiler
    this.context = compiler.context
    this.options = compiler.options
    this.inputFileSystem = compiler.inputFileSystem
    this.outputFileSystem = compiler.outputFileSystem
    this.entries = []
    this.modules = [] // 存放构建过的模块
    this.chunks = []
    this.assets = []
    this.files = []
    this.hooks = {
      successModule: new SyncHook(['module']),
      seal: new SyncHook(),
      beforeChunks: new SyncHook(),
      afterChunks: new SyncHook(),
    }
  }

  /**
   * 完成模块编译操作
   * @param {*} context 当前项目的根
   * @param {*} entry 当前的入口相对路径
   * @param {*} name 
   * @param {*} callback 
   */
  addEntry (context, entry, name, callback) {
    this._addModuleChain(context, entry, name, (err, module) => {
      callback(err, module)
    })
  }

  _addModuleChain (context, entry, name, callback) {
    this.createModule({
      name,
      context,
      rawRequest: entry,
      resource: path.posix.join(context, entry), // entry入口的绝对路径
      moduleId: './' + path.posix.relative(context, path.posix.join(context, entry)),
      parser: new Parser()
    }, (entryModule) => {
      this.entries.push(entryModule)
    }, callback)
  }

  createModule (data, doAddEntry, callback) {
    let module = new NormalModuleFactory().create(data)

    const afterBuild = (err, module) => {
      // 当前模块加载完毕后，判断是否要处理依赖的加载
      if (module.dependencies.length > 0) {
        this.processDependencies(module, err => {
          callback(err, module)
        })
      } else {
        callback(err, module)
      }
    }

    this.buildModule(module, afterBuild)

    doAddEntry && doAddEntry(module)
    this.modules.push(module)
  }

  buildModule (module, callback) {
    module.build(this, err => {
      this.hooks.successModule.call(module)
      callback(err, module)
    })
  }

  /**
   * 1. 核心功能：实现一个依赖模块的递归加载
   * 2. 加载模块的思路都是创建一个模块，然后把被加载模块的内容拿进来
   * 3. 当前模块可能依赖多个模块，我们要让模块都加载完成后再执行callback 【neo-async】
   * @param {*} module 
   * @param {*} callback 
   */
  processDependencies (module, callback) {
    let dependencies = module.dependencies
    async.forEach(dependencies, (dependnecy, done) => {
      this.createModule({
        name: dependnecy.name,
        context: dependnecy.context,
        rawRequest: dependnecy.rawRequest,
        moduleId: dependnecy.moduleId,
        resource: dependnecy.resource,
        parser: new Parser()
      }, null, done)
    }, callback)
  }

  seal (callback) {
    this.hooks.seal.call()
    this.hooks.beforeChunks.call()

    for (const entryModule of this.entries) {
      const chunk = new Chunk(entryModule)
      this.chunks.push(chunk)

      chunk.modules = this.modules.filter(module => module.name === chunk.name)
    }

    this.hooks.afterChunks.call(this.chunks)

    this.createChunksAssets()
  }

  createChunksAssets () {
    for (let i = 0; i < this.chunks.length; i++) {
      const chunk = this.chunks[i]
      const filename = chunk.name
      chunk.files.push(filename)

      let tempPath = path.posix.join(__dirname, 'temp/main.ejs')

      let tempCode = this.inputFileSystem.readFileSync(tempPath, 'utf8')

      let tempRender = ejs.compile(tempCode)

      let source = tempRender({
        entryModuleId: chunk.entryModule.moduleId,
        modules: chunk.modules
      })
      // 输出文件
      this.emitAsstes(filename, source)
    }
  }

  emitAsstes (filename, source) {
    this.assets[filename] = source
    this.files.push(filename)
  }

}

module.exports = Compilation
